package org.daochong.spring.web;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.daochong.lang.BeanUtil;
import org.daochong.lang.uai.UniteAccessInterfaceFactory;
import org.daochong.web.HttpResponseWrapper;
import org.daochong.web.WebUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;

import redis.clients.jedis.JedisCommands;

public class CacheFilter implements Filter {

	public static String CACHE_PARAMETER = "__cache";

	public static String CACHE_PUBLIC_PARAMETER = "__cache_user";

	public static String CACHE_PREFIX = "__cache";

	private UniteAccessInterfaceFactory uaiFactory = null;

	public static String DEFAULT_ENCODING = "UTF-8";

	private boolean initFactory = false;

	private ServletContext context;

	private boolean debug = false;

	private String cacheParam = null;

	private String userFlag = null;

	private String prefix = null;

	public void init(FilterConfig filterConfig) throws ServletException {
		this.context = filterConfig.getServletContext();
		debug = "true".equals(filterConfig.getInitParameter("debug"));
		cacheParam = (filterConfig.getInitParameter("cacheParam") != null ? filterConfig.getInitParameter("cacheParam")
				: CACHE_PARAMETER);
		userFlag = (filterConfig.getInitParameter("userFlag") != null ? filterConfig.getInitParameter("cacheParam")
				: CACHE_PUBLIC_PARAMETER);
		prefix = (filterConfig.getInitParameter("cacheParam") != null ? filterConfig.getInitParameter("cacheParam")
				: CACHE_PREFIX);

	}

	public UniteAccessInterfaceFactory getFactory() {
		if (this.uaiFactory == null && !initFactory) {
			ApplicationContext ac = WebApplicationContextUtils.getWebApplicationContext(context);
			if (ac != null) {
				try {
					this.uaiFactory = ac.getBean(UniteAccessInterfaceFactory.class);
				} catch (Throwable e) {
					e.printStackTrace();
				}
			}
			initFactory = true;
		}
		return this.uaiFactory;
	}

	private void writeDebug(HttpServletRequest request, String message) {
		if (this.debug) {
			StackTraceElement caller = BeanUtil.getCallerInfo();
			SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
			System.out.println(sdf.format(new Date()) + " " + caller.getClassName() + " " + caller.getMethodName()
					+ "(line:" + caller.getLineNumber() + ")");
			System.out.println("URL_CACHE-" + message + ":" + request.getRequestURI()
					+ (request.getQueryString() != null ? "?" + request.getQueryString() : ""));
		}
	}

	public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
			throws IOException, ServletException {
		HttpServletRequest request = (HttpServletRequest) req;
		HttpServletResponse response = (HttpServletResponse) resp;
		if (request.getParameter(cacheParam) != null) {
			String html = getCache(request);
			if (html != null) {
				html = html.trim();
				if (html.startsWith("[") || html.startsWith("{")) {
					WebUtils.writeJSON(html, DEFAULT_ENCODING, response);
				} else {
					WebUtils.writeHtml(html, DEFAULT_ENCODING, response);
				}
				writeDebug(request, "LOAD");
				return;
			}
		}
		if(!(response instanceof HttpResponseWrapper)){
			response = new HttpResponseWrapper(response);
		}
		chain.doFilter(request, response);
		refereshCache(request, response);
	}

	public void refereshCache(HttpServletRequest request, HttpServletResponse response) {
		if (isCached(request) && response instanceof HttpResponseWrapper) {
			HttpResponseWrapper hrw = (HttpResponseWrapper) response;
			int seconds = 0;
			String rid = null;
			Object o = request.getAttribute(prefix + "_rid");
			if (o == null || !(o instanceof String))
				return;
			rid = (String) o;
			o = request.getAttribute(rid + "_seconds");
			if (o == null || !(o instanceof Integer))
				return;
			seconds = (Integer) o;
			UniteAccessInterfaceFactory uaiFactory = getFactory();
			if (uaiFactory != null) {
				JedisCommands jc = uaiFactory.getTemplate(JedisCommands.class);
				if (jc != null) {
					jc.setex(rid, seconds, hrw.getContent());
					writeDebug(request, "Update-Redis");
					return;
				}
			}
			StringCache sc = new StringCache();
			sc.setTime(System.currentTimeMillis());
			sc.setData(hrw.getContent());
			request.getSession().setAttribute(rid, sc);
		} else {
			String rid = prefix + "_" + WebUtils.toRequestId(request, cacheParam);
			if (!this.isPublic(request)) {
				rid += "_" + request.getSession().getId();
			}
			UniteAccessInterfaceFactory uaiFactory = this.getFactory();
			boolean del = !(response instanceof HttpResponseWrapper);
			if (uaiFactory != null) {
				JedisCommands jc = uaiFactory.getTemplate(JedisCommands.class);
				if (jc.exists(rid)) {
					if (del) {
						jc.del(rid);
					} else {
						jc.set(rid, ((HttpResponseWrapper) response).getContent());
						writeDebug(request, "Update-Redis");
					}
					return;
				}
			}
			try {
				HttpSession hs = request.getSession();
				if (hs.getAttribute(rid) != null) {
					hs.setAttribute(rid, ((HttpResponseWrapper) response).getContent());
					writeDebug(request, "Update-Session");
				}
			} catch (Throwable e) {
				e.printStackTrace();
			}
		}
	}

	public boolean isCached(HttpServletRequest request) {
		return request.getParameter(cacheParam) != null;
	}

	public boolean isPublic(HttpServletRequest request) {
		return request.getParameter(userFlag) == null;
	}

	public void destroy() {

	}

	public String getCache(HttpServletRequest request) {
		if (request.getParameter(cacheParam) == null) {
			return null;
		}
		int seconds = getCacheTime(request);
		if (seconds <= 0)
			return null;
		String rid = prefix + "_" + WebUtils.toRequestId(request, cacheParam);
		if (!this.isPublic(request)) {
			rid += "_" + request.getSession().getId();
		}
		request.setAttribute(rid + "_seconds", seconds);
		request.setAttribute(prefix + "_rid", rid);
		UniteAccessInterfaceFactory uaiFactory = this.getFactory();
		if (uaiFactory != null) {
			JedisCommands jc = uaiFactory.getTemplate(JedisCommands.class);
			if (jc != null) {
				if (jc.exists(rid)) {
					Long l = jc.expire(rid, seconds);
					if (l != null && l < 0) {
						jc.del(rid);
						return null;
					}
					return jc.get(rid);
				}
			}
		}
		StringCache sc = (StringCache) request.getSession().getAttribute(rid);
		if (sc != null) {
			if (System.currentTimeMillis() - sc.getTime() < seconds * 1000) {
				return sc.getData();
			}
		}
		return null;
	}

	public int getCacheTime(HttpServletRequest request) {
		int seconds = 0;
		String p = request.getParameter(cacheParam).trim();
		if (p.endsWith("h") || p.endsWith("H")) {
			seconds = Integer.parseInt(p.substring(0, p.length() - 1)) * 3600;
		} else if (p.endsWith("m")) {
			seconds = Integer.parseInt(p.substring(0, p.length() - 1)) * 60;
		} else if (p.endsWith("s") || p.endsWith("S")) {
			seconds = Integer.parseInt(p.substring(0, p.length() - 1));
		} else if (p.endsWith("d") || p.endsWith("D")) {
			seconds = Integer.parseInt(p.substring(0, p.length() - 1)) * 86400;
		} else if (p.endsWith("M")) {
			seconds = Integer.parseInt(p.substring(0, p.length() - 1)) * 86400 * 30;
		} else if (p.endsWith("y") || p.endsWith("Y")) {
			seconds = Integer.parseInt(p.substring(0, p.length() - 1)) * 86400 * 365;
		} else if (p.endsWith("w") || p.endsWith("W")) {
			seconds = Integer.parseInt(p.substring(0, p.length() - 1)) * 86400 * 7;
		} else {
			seconds = Integer.parseInt(p);
		}
		return seconds;
	}

	class StringCache {
		private long time;
		private String data;

		public long getTime() {
			return time;
		}

		public void setTime(long time) {
			this.time = time;
		}

		public String getData() {
			return data;
		}

		public void setData(String data) {
			this.data = data;
		}
	}

}
